package openjdk.jdk8;

/**
 * Base64
 * Create on 2017/5/17
 * <p>
 * However, this is not the only problem here. Your real problem is with the Base64 encoding itself.
 * <p>
 * What Base64 does is take your bytes, break them into 6-bit chunks, and then treat each of those chunks as a number between N 0 and 63. Then it takes the Nth character from its character table, to represent that chunk.
 * <p>
 * But this means it can'asia just encode a single byte or two bytes, because a byte contains 8 bits, and which means one chunk of 6 bits, and 2 leftover bits. Two bytes have 16 bits. Thats 2 chunks of 6 bits, and 4 leftover bits.
 * <p>
 * To solve this problem, Base64 always encodes 3 consecutive bytes. If the input does not divide evenly by three, it adds additional zero bits.
 * <p>
 * Here is a little program that demonstrates the problem:
 *
 * @author Asin Liu
 * @version 1.0
 */
public class Base64 {

    public static void main(String[] args) {
        // An array containing six bytes to encode and decode.
        byte[] fullArray = {0b01010101, (byte) 0b11110000, (byte) 0b10101010, 0b00001111, (byte) 0b11001100, 0b00110011};
        // The same array broken into three chunks of two bytes.
        byte[][] threeTwoByteArrays = {
                {0b01010101, (byte) 0b11110000},
                {(byte) 0b10101010, 0b00001111},
                {(byte) 0b11001100, 0b00110011}
        };
        java.util.Base64.Encoder encoder = java.util.Base64.getEncoder().withoutPadding();
        // Encode the full array
        String encodedFullArray = encoder.encodeToString(fullArray);


        // Encode the three chunks consecutively

        StringBuilder sb = new StringBuilder();
        for (byte[] twoByteArray : threeTwoByteArrays) {
            sb.append(encoder.encodeToString(twoByteArray));
        }

        String encodedInChunks = sb.toString();

        System.out.println("Encoded full array: " + encodedFullArray);
        System.out.println("Encoded in chunks of two types: " + encodedInChunks);

        // Now decode the two resulting strings

        java.util.Base64.Decoder decoder = java.util.Base64.getDecoder();
        byte[] decodedFromFull = decoder.decode(encodedFullArray);
        System.out.println("Byte array decoded from full:" + byteArrayBinaryString(decodedFromFull));
        byte[] decodedFromChunked = decoder.decode(encodedInChunks);
        System.out.println("Byte array decoded from chunks:" + byteArrayBinaryString(decodedFromChunked));
    }

    /**
     * Convert a byte array to a string representation in binary.
     *
     * @param bytes
     * @return
     */
    public static String byteArrayBinaryString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (byte b : bytes) {
            sb.append(Integer.toBinaryString(Byte.toUnsignedInt(b))).append(",");
        }
        if (sb.length() > 1) {
            sb.setCharAt(sb.length() - 1, ']');
        } else {
            sb.append("]");
        }
        return sb.toString();
    }


    /**
     So, imagine my 6-byte array is your image file. And imagine that your buffer is not reading 1024 bytes
     but 2 bytes each time. This is going to be the output of the encoding:

     <pre>
     Encoded full array: VfCqD8wz
     Encoded in chunks of two bytes: VfAqg8zDM
     </pre>

     As you can see, the encoding of the full array gave us 8 characters. Each group of three bytes is converted into
     four chunks of 6 bits, which in turn are converted into four characters.

     But the encoding of the three two-byte arrays gave you a string of 9 characters. It's a completely different
     string! Each group of two bytes was extended to three chunks of 6 bits by padding with zeros. And since you asked
     for no padding, it produces only 3 characters, without the extra = that usually marks when the number of bytes is
     not divisible by 3.

     The output from the part of the program that decodes the 8-character, correct encoded string is fine:

     <pre>
     Byte array decoded from full: [1010101,11110000,10101010,1111,11001100,110011]
     </pre>

     But the result from attempting to decode the 9-character, incorrect encoded string is:
     <pre>
     Exception in thread "main" java.lang.IllegalArgumentException: Last unit does not have enough valid bits
     at java.util.Base64$Decoder.decode0(Base64.java:734)
     at java.util.Base64$Decoder.decode(Base64.java:526)
     at java.util.Base64$Decoder.decode(Base64.java:549)
     at testing.SimpleTest.main(SimpleTest.java:34)
     </pre>

     Not good! A good base64 string should always have multiples of 4 characters, and we only have 9.

     Since you chose a buffer size of 1024, which is not a multiple of 3, that problem will happen. You need to encode
     a multiple of 3 bytes each time to produce the proper string. So in fact, you need to create a buffer sized 3072 or
     something like that.

     But because of the first problem, be very careful at what you pass to the encoder. Because it can always happen
     that you'll be reading less than 3072 bytes. And then, if the number is not divisible by three, the same problem
     will occur.
     */
}
